<?php
/**
 * Created by PhpStorm.
 * User: caoyangmin
 * Date: 16/9/27
 * Time: 下午3:47
 */

namespace Once\Utils;


use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\DocBlock\StandardTagFactory;
use phpDocumentor\Reflection\DocBlockFactory;
use phpDocumentor\Reflection\FqsenResolver;
use phpDocumentor\Reflection\TypeResolver;

/**
 * Class AnnotationsVisitor
 * @package Once\Utils
 * TODO 是否需要支持注释继承?
 */
class AnnotationsVisitor
{
    //
    const TYPE_CLASS = 'class';
    const TYPE_METHOD = 'method';
    const TYPE_PROPERTY = 'property';

    /**
     *
     * @param string $className class name
     * @param callable $visitor function($type, $target, $name, $value)
     */
    static function visit($className, callable $visitor){
        is_string($className) or Verify::fail(new \InvalidArgumentException('$className not string'));

        //TODO * 支持"" @ann "这里可以有 空格 \" @"
        //TODO * 检测opcache是否去掉了注释
        $refl = new \ReflectionClass($className);
        $doc  = self::createDocBlockFactory();

        if($refl->getDocComment()) {
            $docblock = $doc->create($refl->getDocComment());
            $tags = $docblock->getTags();
            foreach ($tags as $tag) {
                $visitor(self::TYPE_CLASS, $className, $tag->getName(), strval($tag->getDescription()));
            }
        }

        foreach ($refl->getMethods() as $method){
            $comment = $method->getDocComment();
            if(!$comment){
                continue;
            }
            $docblock = $doc->create($comment);
            $tags = $docblock->getTags();
            foreach ($tags as $tag){
                $visitor(self::TYPE_METHOD, $method->getName(), $tag->getName(), strval($tag->getDescription()));
            }
        }

        foreach ($refl->getProperties(\ReflectionProperty::IS_PUBLIC) as $property){
            $comment = $property->getDocComment();
            if(!$comment){
                continue;
            }
            $docblock = $doc->create($comment);
            $tags = $docblock->getTags();
            foreach ($tags as $tag){
                $visitor(self::TYPE_PROPERTY, $property->getName(), $tag->getName(), strval($tag->getDescription()));
            }
        }

    }

    static function visitIntoBlock($docblock, callable $visitor){
        is_string($docblock) or Verify::fail(new \InvalidArgumentException('$docblock not string'));
        if($docblock == '')return;
        while (true) {
            $pos = strpos($docblock, '@'); //TODO * 不够健壮
            if ($pos !== false) {
                $docblock = substr($docblock, $pos);
            } else {
                return;
            }
            $doc = self::createDocBlockFactory();

            $docobj = $doc->create($docblock);
            $tags = $docobj->getTags();
            foreach ($tags as $tag) {
                $visitor(null, null, $tag->getName(), strval($tag->getDescription()));
            }
            $docblock = substr($docblock, 1);
        }

    }

    public static function createDocBlockFactory(){
        $fqsenResolver = new FqsenResolver();
        $tagFactory = new StandardTagFactory($fqsenResolver,[]);
        $descriptionFactory = new DescriptionFactory($tagFactory);
        $tagFactory->addService($descriptionFactory);
        $tagFactory->addService(new TypeResolver($fqsenResolver));

        $docBlockFactory = new DocBlockFactory($descriptionFactory, $tagFactory);
        return $docBlockFactory;
    }

}